package goem

/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <emacs-module.h>

#include "module_c.h"

static void em_test_intern(emacs_env *env, const char *name) {
   // emacs_value v = env->intern(env, "fset");
   emacs_value v = env->intern(env, name);
   fprintf(stderr, "=== %p, %d==?%d, %p\n", v, strlen("fset"), strlen(name), env);
}
static void em_provide(emacs_env *env, emacs_value feat, emacs_value prov) {
    char *feature = "goem";
	emacs_value Qfeat = env->intern(env, feature);
	emacs_value Qprovide = env->intern (env, "provide");
	emacs_value args[] = { feat };
    fprintf(stderr, "%p=%p,\n", prov, Qprovide);
    fprintf(stderr, "%p=%p,\n", feat, Qfeat);
	// env->funcall(env, Qprovide, 1, args);
    env->funcall(env, prov, 1, args);
}
*/
import "C"
import "unsafe"
import "log"
import "runtime"

// import "os"

// import "strings"
import "github.com/kitech/colog"

func init() {
	log.SetFlags(log.Flags() | log.Lshortfile)
	colog.Register()
	runtime.GOMAXPROCS(1)
}

//export plugin_is_GPL_compatible
func plugin_is_GPL_compatible() {}

//export emacs_module_init
func emacs_module_init(ert *C.struct_emacs_runtime) C.int {
	runtime.LockOSThread()

	C.set_module_func_pointer(ert)

	// setup runtime
	{
		emrt.ert = ert
		emrt.env = emrt.GetEnv()
		emrt.envc = emrt.env.env
		if false {
			log.Printf("addr: %p\n", emrt.envc)
		}
	}
	types_init()
	builtins_init(GetModule())

	emrt.env.NonLocalExitClear()
	Provide0("goem")

	if ModuleInit != nil {
		return C.int(ModuleInit(GetModule()))
	}
	return 0
}

// user set in func init(){}
var ModuleInit func(em Module) int

type Module struct {
	emrt *Runtime
}

func GetModule() Module         { return Module{emrt} }
func (this Module) GetEnv() Env { return this.emrt.env }

func MkInt(value int64) Value     { return emrt.env.MakeInt(value) }
func MkStr(value string) Value    { return emrt.env.MakeString(value) }
func MkFloat(value float64) Value { return emrt.env.MakeFloat(value) }
func MkRef(value Value) Value     { return emrt.env.MakeGlobalRef(value) }
func FreeRef(value Value)         { emrt.env.FreeGlobalRef(value) }
func MkFunc(f func(args []Value, ud interface{}) Value, doc string, ud interface{}) Value {
	return emrt.env.MakeFunction(f, doc, ud)
}

///
type FuncallExitType int

const (
	FuncallExitReturn = FuncallExitType(C.emacs_funcall_exit_return)
	FuncallExitSignal = FuncallExitType(C.emacs_funcall_exit_signal)
	FuncallExitThrow  = FuncallExitType(C.emacs_funcall_exit_throw)
)

var emrt = &Runtime{}

type Runtime struct {
	env  Env
	ert  *C.struct_emacs_runtime
	envc *C.emacs_env
}

func (this Runtime) GetEnv() Env {
	e := C.em_get_environment_forgo(this.ert)
	return newEnvFrom(e)
}

type Value struct {
	ev C.emacs_value
}

func (this Value) Type() Value {
	ev := C.em_type_of_forgo(emrt.GetEnv().env, this.ev)
	return newValueFrom(ev)
}
func (this Value) IsNotNil() bool {
	r := C.em_is_not_nil_forgo(emrt.GetEnv().env, this.ev)
	if r {
		return true
	}
	return false
}

func (this Value) ToString() string {
	var sz C.ptrdiff_t
	envc := emrt.GetEnv().env

	r := C.em_copy_string_contents_forgo(envc, this.ev, nil, &sz)
	if sz > 0 {
		buf := make([]byte, int(sz))
		bufc := (*C.char)((unsafe.Pointer)(&buf[0]))
		r = C.em_copy_string_contents_forgo(envc, this.ev, bufc, &sz)
		if !r {
			log.Println("wtf")
		}
		// if strings.HasPrefix(string(buf), "25.1") {
		if buf[len(buf)-1] == 0 {
			buf = buf[0 : len(buf)-1]
		}
		if len(buf) < 10 {
			// log.Println(string(buf), buf)
		}
		// }
		return string(buf)
	}
	return ""
}

func (this Value) ToInt() int64 {
	r := C.em_extract_integer_forgo(emrt.GetEnv().env, this.ev)
	return int64(r)
}
func (this Value) ToFloat() float64 {
	r := C.em_extract_float_forgo(emrt.GetEnv().env, this.ev)
	return float64(r)
}
func (this Value) SymVal() Value {
	return symbolValue(this)
}

func (this Value) Eq(vb Value) bool {
	r := C.em_eq_forgo(emrt.GetEnv().env, this.ev, vb.ev)
	if r {
		return true
	}
	return false
}

func newValueFrom(ev C.emacs_value) Value { return Value{ev} }

type Env struct {
	env *C.emacs_env
}

func newEnvFrom(e *C.emacs_env) Env { return Env{e} }

func (this Env) MakeGlobalRef(anyReference Value) Value {
	ev := C.em_make_global_ref_forgo(this.env, anyReference.ev)
	return newValueFrom(ev)
}
func (this Env) FreeGlobalRef(anyReference Value) {
	C.em_free_global_ref_forgo(this.env, anyReference.ev)
}

func (this Env) NonLocalExitCheck() FuncallExitType {
	rc := C.em_non_local_exit_check_forgo(this.env)
	return FuncallExitType(rc)
}
func (this Env) NonLocalExitClear() {
	C.em_non_local_exit_clear_forgo(this.env)
}
func (this Env) NonLocalExitGet() (FuncallExitType, Value, Value) {
	var sym_out C.emacs_value
	var data_out C.emacs_value
	rc := C.em_non_local_exit_get_forgo(this.env, &sym_out, &data_out)
	return FuncallExitType(rc), newValueFrom(sym_out), newValueFrom(data_out)
}

func (this Env) NonLocalExitSignal(symbol, data Value) {
	C.em_non_local_exit_signal_forgo(this.env, symbol.ev, data.ev)
}

func (this Env) NonLocalExitThrow(tag, value Value) {
	C.em_non_local_exit_throw_forgo(this.env, tag.ev, value.ev)
}

type cbcontext struct {
	f   func(args []Value, ud interface{}) Value
	doc string
	ud  interface{}
}

var cbudmap = make(map[uint64]cbcontext)
var cbreqbase uint64 = 1234567

func nextcbreq() uint64 { cbreqbase++; return cbreqbase }

//export em_function_callback
func em_function_callback(env *C.emacs_env, nargs C.ptrdiff_t, args []C.emacs_value, ud unsafe.Pointer) C.emacs_value {
	req := (uint64)((uintptr)(ud))
	ctx := cbudmap[req]
	if ctx.f != nil {
		args_ := make([]Value, int(nargs))
		for i := 0; i < int(nargs); i++ {
			args_[i] = newValueFrom(args[i])
		}
		return ctx.f(args_, ctx.ud).ev
	}
	return nil
}

// TODO more gopher func, using go type?
// but maybe only support little go types, int/float/string
func (this Env) MakeFunction(f func(args []Value, ud interface{}) Value,
	doc string, ud interface{}) Value {
	req := nextcbreq()
	cbudmap[req] = cbcontext{f, doc, ud}
	reqp := (unsafe.Pointer)((uintptr)(req))

	doc_ := C.CString(doc)
	defer C.free(unsafe.Pointer(doc_))
	ev := C.em_make_function_forgo(this.env, 0, 16, nil, doc_, reqp)
	// log.Println(ev)
	return newValueFrom(ev)
}

func (this Env) Funcall(efn Value, args []Value) Value {
	// log.Println(args, args == nil)
	if args != nil && len(args) > 0 {
		args_c_values := make([]C.emacs_value, len(args))
		for idx, arg := range args {
			args_c_values[idx] = arg.ev
		}
		// log.Println(len(args_c_values), args_c_values, C.ptrdiff_t(len(args)))
		args_c := (*C.emacs_value)((unsafe.Pointer)(&args_c_values[0]))
		ev := C.em_funcall_forgo(this.env, efn.ev, C.ptrdiff_t(len(args)), args_c)
		// log.Println(ev)
		return newValueFrom(ev)
	} else {
		ev := C.em_funcall_forgo(this.env, efn.ev, 0, nil)
		// log.Println(ev)
		return newValueFrom(ev)
	}
}
func (this Env) Funcall0(funcName string, args []Value) Value {
	funcSym := this.Intern(funcName)
	return this.Funcall(funcSym, args)
}

func (this Env) Intern(symbolName string) Value {
	symName_ := C.CString(symbolName)
	defer C.free(unsafe.Pointer(symName_))
	ev := C.em_intern_forgo(this.env, symName_)
	return newValueFrom(ev)
}

func (this Env) MakeInt(value int64) Value {
	// env := emrt.GetEnv().env
	ev := C.em_make_integer_forgo(this.env, C.intmax_t(value))
	return newValueFrom(ev)
}
func (this Env) MakeFloat(value float64) Value {
	ev := C.em_make_float_forgo(this.env, C.double(value))
	return newValueFrom(ev)
}

func (this Env) MakeString(contents string) Value {
	contents_ := C.CString(contents)
	defer C.free(unsafe.Pointer(contents_))
	ev := C.em_make_string_forgo(this.env, contents_, C.ptrdiff_t(len(contents)))
	return newValueFrom(ev)
}

//export em_user_ptr_finalizer_callback
func em_user_ptr_finalizer_callback(p unsafe.Pointer) {
}

// TODO make_user_ptr ...

func (this Env) VecGet(vec Value, i int) Value {
	ev := C.em_vec_get_forgo(this.env, vec.ev, C.ptrdiff_t(i))
	return newValueFrom(ev)
}
func (this Env) VecSet(vec Value, i int, value Value) {
	C.em_vec_set_forgo(this.env, vec.ev, C.ptrdiff_t(i), value.ev)
}
func (this Env) VecSize(vec Value) int {
	sz := C.em_vec_size_forgo(this.env, vec.ev)
	return int(sz)
}

// keep empty for build c-shared
func main() {}
